package com.twm.tamauth.engine.interceptor

import android.util.Log
import com.twm.tamauth.engine.MyAuthNetConfig
import com.twm.tamauth.engine.cache.CacheMode
import com.twm.tamauth.engine.cache.ForceCache
import com.twm.tamauth.engine.exception.MyAuthHttpFailureException
import com.twm.tamauth.engine.exception.MyAuthNetConnectException
import com.twm.tamauth.engine.exception.MyAuthNetException
import com.twm.tamauth.engine.exception.MyAuthNetSocketTimeoutException
import com.twm.tamauth.engine.exception.MyAuthNetUnknownHostException
import com.twm.tamauth.engine.exception.MyAuthNoCacheException
import com.twm.tamauth.engine.ext.tagOf
import com.twm.tamauth.engine.ext.toNetResponseBody
import okhttp3.CacheControl
import okhttp3.Interceptor
import okhttp3.Response
import java.lang.ref.WeakReference
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.net.UnknownHostException

/**
 * Net代理OkHttp的拦截器
 */
object MyAuthNetOkHttpInterceptor : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()
        val reqBody = request.body
        val cache = request.tagOf<ForceCache>() ?: MyAuthNetConfig.forceCache
        val cacheMode = request.tagOf<CacheMode>()
        request = request.newBuilder().apply {
            if (cache != null && cacheMode != null) {
                cacheControl(CacheControl.Builder().noCache().noStore().build())
            }
        }.method(request.method, reqBody).build()

        var response: Response? = null
        try {
            appendRunningCall(chain)
            response = if (cache != null) {
                when (cacheMode) {
                    CacheMode.READ -> cache.get(request) ?: throw MyAuthNoCacheException(request)
                    CacheMode.READ_THEN_REQUEST -> cache.get(request) ?: chain.proceed(request)
                        .run {
                            cache.put(this)
                        }

                    CacheMode.REQUEST_THEN_READ -> try {
                        chain.proceed(request).run {
                            cache.put(this)
                        }
                    } catch (e: Exception) {
                        cache.get(request) ?: throw MyAuthNoCacheException(request)
                    }

                    CacheMode.WRITE -> chain.proceed(request).run {
                        cache.put(this)
                    }

                    else -> chain.proceed(request)
                }
            } else {
                chain.proceed(request)
            }
            val respBody = response.body?.toNetResponseBody {
                removeRunningCall(chain)
            }
            response = response.newBuilder().body(respBody).build()
            return response
        } catch (e: SocketTimeoutException) {
            Log.e("NetOkHttpInterceptor", "SocketTimeoutException: ${e.message}")
            throw MyAuthNetSocketTimeoutException(request, e.message, e)
        } catch (e: ConnectException) {
            Log.e("NetOkHttpInterceptor", "ConnectException: ${e.message}")
            throw MyAuthNetConnectException(request, cause = e)
        } catch (e: UnknownHostException) {
            Log.e("NetOkHttpInterceptor", "UnknownHostException: ${e.message}")
            throw MyAuthNetUnknownHostException(request, message = e.message)
        } catch (e: MyAuthNetException) {
            Log.e("NetOkHttpInterceptor", "NetException: ${e.message}")
            throw e
        } catch (e: Throwable) {
            Log.e("NetOkHttpInterceptor", "Throwable: ${e.message}")
            throw MyAuthHttpFailureException(request, cause = e)
        } finally {
            if (response?.body == null) {
                removeRunningCall(chain)
            }
        }
    }

    /**
     * 将请求添加到请求队列
     */
    private fun appendRunningCall(chain: Interceptor.Chain) {
        MyAuthNetConfig.runningCalls.add(WeakReference(chain.call()))
    }

    /**
     * 将请求从请求队列移除
     */
    private fun removeRunningCall(chain: Interceptor.Chain) {
        val iterator = MyAuthNetConfig.runningCalls.iterator()
        while (iterator.hasNext()) {
            val call = iterator.next().get()
            if (call == null) {
                iterator.remove()
                continue
            }
            if (call == chain.call()) {
                iterator.remove()
                return
            }
        }
    }
}